package com.googlecode.lightest.core

abstract class LightestTask implements IBreakpointTask {
    protected TaskNode config
    protected IPreferences prefs
    protected ITestEnvironment env

    ITaskResult parentResult
    ITaskDispatcher dispatcher
    ITaskResult taskResult
    
    def builder

    LightestTask() {
        builder = new TaskNodeBuilder()
    }

    /**
     * Configures the task by simply making member variables available to
     * concrete subclasses. Override this method for different configuration
     * behavior.
     *
     * @param config
     * @param prefs
     * @param env           the environment in which this task is to be
     *                      performed
     * @param parentResult  the result of performing the parent task. May be
     *                      null if this task has no parent.
     * @param dispatcher    the dispatcher that will be dispatching the task
     */
    void configure(TaskNode config, IPreferences prefs, ITestEnvironment env,
        ITaskResult parentResult, ITaskDispatcher dispatcher)
    {
        this.config = config
        this.prefs = prefs
        this.env = env
        this.parentResult = parentResult
        this.dispatcher = dispatcher

        taskResult = new TaskResult(this, parentResult)
    }

    String getName() {
        return this.class.name
    }
    
    String getShortName() {
        def name = getName()
        return name.substring(name.lastIndexOf('.') + 1)
    }

    String getDescription() {
        assert config != null

        return config.'@description' ?: ""
    }

    String getParams() {
        assert config != null

        def params = new TreeMap()
        def attributes = config.attributes().findAll { k, v ->
            k != 'description' && k != 'breakpoint'
        }
        
        params.putAll(attributes)

        return params.toString() + (config.nodeValue()
            ? ", ${config.nodeValue()}" : "")
    }
    
    boolean isBreakpoint() {
        assert config != null
        
        return config.'@breakpoint'
    }
    
    /**
     * Concrete subclasses must implement this method, which is wrapped by
     * the perform() method. The result is passed in, so implementing methods
     * may simply populate it without needing to create it or return it.
     *
     * @param result  the result which will be returned by the outer call to
     *                perform().
     */
    abstract void doPerform(ITaskResult result)

    /**
     * Performs the task in an environment, and returns the result. Tasks may
     * choose to use or ignore the results of their parent tasks. This
     * implementation wraps doPerform() so it can safely execute the task and
     * add some behaviors to the result.
     */
    ITaskResult perform() {
        assert taskResult != null

        taskResult.setStartTime(System.currentTimeMillis())

        try {
            doPerform(taskResult)
        }
        catch (e) {
            taskResult.setMessage("Unexpected exception: ${e.getMessage()}")
            taskResult.setResponseData(LightestUtils.getSanitizedStackTrace(e))
            taskResult.fail()
        }

        taskResult.setEndTime(System.currentTimeMillis())

        return taskResult
    }

    /**
     * Allows for tasks to be created via composition of existing tasks. Tasks
     * created through composition are performed in the following manner:
     *
     *  1) The task is performed in full before any of its child tasks, as
     *     specified in the testcase, are performed.
     *  2) The "sub-tasks" comprising the task (not to be confused with child)
     *     tasks) are performed as if they were child tasks, and will be
     *     identified as such in the result (i.e. the listener attached to the)
     *     task dispatcher will be notified in this order).
     *  3) Bona-fide child tasks will only be performed in the event that none
     *     of the above tasks have failed. This is consistent with the behavior
     *     that is expected when child tasks are nested under non-composition
     *     tasks. They will appear as siblings of the "sub-task" branch.
     *
     * To illustrate:
     *
     *   - A, B, C, D, & E are tasks.
     *   - A.doPerform() has the following structure:
     *         doPerform() {
     *             B () {
     *                 C()
     *             }
     *         }
     *   - The testcase has the structure:
     *         @Test
     *         void testIt() {
     *             A () {
     *                D ()
     *                E ()
     *             }
     *         }
     *
     * Then the task result tree will look like:
     *
     *     A
     *     |
     *     +--B
     *     |  |
     *     |  +--C
     *     |
     *     +--D
     *     |
     *     +--E
     *
     * And this is the exact flow of execution, depth-first. In contrast to
     * specifying exactly this structure in the testcase, however, if either B
     * or C were to fail, D & E would NOT be performed. This is because the
     * latter are being treated as dependencies of the entire composite task A.
     */
    def methodMissing(String name, args) {
        LightestUtils.setBuilderAsDelegate(args, builder)

        def taskTree = builder.invokeMethod(name, args)
        def result = dispatcher.dispatch(taskTree, this.taskResult)

        if (result.getStatus() == ITaskResult.STATUS_FAILED) {
            // child tasks will NOT be performed
            while (config.children().size() > 0) {
                config.remove(config.children()[0])
            }

            // fail the current task. Note however that sibling sub-tasks will
            // continue to be executed.
            taskResult.fail()
        }
    }
}